using System;
using System.Collections.Generic;
using System.Text;
using ILNumerics.BuiltInFunctions;
using ILNumerics;
using System.IO;

namespace ILAutoTestCaseGeneration {
    public class ILTestAutoGenerator {

        private string m_baseDirectory;
        string m_relativeTestCasePath; 

        private static bool s_firstRun = true; 
        public static bool IsFirstRun {
            get { 
                bool ret = s_firstRun;
                s_firstRun = false; 
                return ret; 
            }
        }
        public string RelativeTestCasePath { 
            get { 
                if (String.IsNullOrEmpty(m_relativeTestCasePath))
                    throw new NullReferenceException("relative testcase path not set!");
                return m_relativeTestCasePath; 
            } 
            set { m_relativeTestCasePath = value; }
        }


        public bool DeleteResultsOnRecreate = true; 
        public string m_baseDirectoryRelative2EXE = @"..\ILNumericsTest\Tests\AutoGenerated\"; 
        public string OUTPUT_PARAMETER_PREFIX = "output";
        public string INPUT_PARAMETER_PREFIX = "input";
        public string OVERRIDES_PARAMETER_PREFIX = "overrides";
        public string OCTAVE_CREATION_SCRIPT_NAME = "createResults.m"; 
        public string HEADER = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.IO; 
using ILNumerics;
using ILNumerics.BuiltInFunctions;

namespace ILNumerics.Test {{
    [ILTestAutoCreate]
    public class {0} : ILTest {{

        public string INPUT_PARAMETER_MATFILE = @""{1}"";
        public string OUTPUT_PARAMETER_MATFILE = @""{2}"";
        public string OVERRIDE_PARAMETER_MATFILE = @""{6}"";
        private ILMatFile m_inputMatfile;
        private ILMatFile m_outputMatfile;
        private ILMatFile m_overrideMatfile;
        private ILArray<double> NULL_INPUT_DOUBLE = null; 
        private {5} NULL_INPUT = null; 

        public {0}() {{
            m_inputMatfile = new ILMatFile(INPUT_PARAMETER_MATFILE);
            m_outputMatfile = new ILMatFile(OUTPUT_PARAMETER_MATFILE);
            if (File.Exists(OVERRIDE_PARAMETER_MATFILE)) {{
                m_overrideMatfile = new ILMatFile(OVERRIDE_PARAMETER_MATFILE); 
                foreach (string key in m_overrideMatfile.Keys) {{
                    m_outputMatfile[key] = m_overrideMatfile[key]; 
                }}
            }}
        }}

        public override void Run() {{
            base.Run();
            Header();
            // call all functions 
            LoadIgnoreIds(Path.GetDirectoryName(INPUT_PARAMETER_MATFILE));
            for (int i = 0; i < {3}; i++) {{ 
                ErrorHandlerProcessMethod ret = running_test(i);
                if (ret == ErrorHandlerProcessMethod.Abort) {{
                    System.Threading.Thread.CurrentThread.Abort(); 
                }}
            }}
            Footer(); 
            StoreIgnoreIds(Path.GetDirectoryName(INPUT_PARAMETER_MATFILE));
        }}

        private ErrorHandlerProcessMethod running_test(int id) {{
            try {{
                {4}
                Success(""input #0:"" + input0.Dimensions.ToString()); 
                return ErrorHandlerProcessMethod.Success;
            }} catch (Exception exc) {{
                if (m_outputMatfile[""output_""+id.ToString()+""_0""] is ILArray<char> 
                    && (m_outputMatfile[""output_""+id.ToString()+""_0""] as ILArray<char>).ToString().Replace("" "","""").Contains(""Exception"")) {{
                    Success(""Expected exception was thrown. (a good thing)""); 
                    return ErrorHandlerProcessMethod.Success; 
                }} else {{ 
                    Error(""Uunexpected exception thrown! Input parameter set number: "" + id);
                    return ErrorHandlerProcessMethod.Abort; 
                }}
            }}
        }}
    }}
}}";

        public ILTestAutoGenerator(string baseDir) {
            System.Diagnostics.Debug.Write("Generating Creator Instance based on: " + this.GetType().Name + Environment.NewLine); 
            // create directory structure
            m_baseDirectory = Path.GetDirectoryName(baseDir);
            if (m_baseDirectory[m_baseDirectory.Length - 1] != Path.DirectorySeparatorChar)
                m_baseDirectory += Path.DirectorySeparatorChar;
            if (!Directory.Exists(m_baseDirectory)) {
                Directory.CreateDirectory(m_baseDirectory); 
            }
            if (!File.Exists(m_baseDirectory + "createAll.m")) {
                //File.Create(m_baseDirectory + "createAll.m");
                File.AppendAllText(m_baseDirectory + "createAll.m",@"% - ILNumerics.Net testing framework - 
% createAll - runs createScripts recursively 
function createAll(dname) 
    if (~exist('dname')) dname = pwd; end
    f = dir(dname);
    for fi = 3:length(f)
        curItem = f(fi); 
        if (curItem.isdir) 
            createAll(fullfile(dname, curItem.name));  
        else 
            if strcmp(curItem.name, 'createResults.m')
                mname = fullfile(dname, curItem.name); 
                run (mname)
            end    
        end
    end
end "); 
            }
        }

        public void BatchCreateTests(IEnumerable<ILTestArrayProvider> arrays, IList<ILTestCaseDefinition> cases) {
            List<string> generatedTestNames = new List<string>(); 
            int count = 0; 
            foreach (ILTestCaseDefinition testCase in cases) {
                Console.Out.Write("Creating test case #{0} of {1} -> {2} ...     ",++count,cases.Count,testCase.TestCaseName); 
                int pCount = 0, colPos = Console.CursorLeft;   
                foreach (ILTestArrayProvider prov in arrays) {
                    while (Console.CursorLeft > colPos)
                        Console.Out.Write((char)08); 
                    string curProvName = (++pCount).ToString("d4") + ":" + prov.GetIdentifierPostfix().PadRight(10); 
                    Console.Out.Write(curProvName); 
                    generatedTestNames.Add(AutoCreateTestCase(prov,testCase));
                }
                while (Console.CursorLeft > colPos)
                    Console.Out.Write((char)08); 
                Console.Out.Write("done" + Environment.NewLine); 
            }
        }
        /// <summary>
        /// generate single test case 
        /// </summary>
        /// <param name="arrProv">test data provider (ILCell, specific type)</param>
        /// <param name="definition">case definition</param>
        /// <returns>localPostfixedTestCaseName</returns>
        public string AutoCreateTestCase(ILTestArrayProvider arrProv, ILTestCaseDefinition definition) {
            StringBuilder ocs = new StringBuilder("clear output*;"); 
            string matInputFileName = INPUT_PARAMETER_PREFIX + ".mat"; 
            string matOutputFileName = OUTPUT_PARAMETER_PREFIX + ".mat"; 
            string resultOverridesFileName = OVERRIDES_PARAMETER_PREFIX + ".mat"; 
            ILCell inputVars = arrProv.GenerateTestArrays();
            ILMatFile matFile = new ILMatFile();
            List<string> inTag = new List<string>(); 
            List<string> outTag = new List<string>();
            string outTagJoined;
            string inTagJoined;

            ocs.Append("\n" + String.Format("parameter = load('{0}');", matInputFileName));            
            for (int i = 0; i < inputVars.Dimensions[0]; i++) {
                inTag.Clear(); outTag.Clear(); 
                // handle input parameters 
                for (int c = 0; c < definition.InputParameterCount; c++) {
                    string ident = "_" + i.ToString() + "_" + c.ToString(); 
                    matFile[INPUT_PARAMETER_PREFIX + ident] = inputVars.GetValue(i, c);
                    inTag.Add("parameter." + INPUT_PARAMETER_PREFIX + ident); 
                }
                // handle output parameters 
                for (int c = 0; c < definition.OutputParameterCount; c++) {
                    string ident = "_" + i.ToString() + "_" + c.ToString();
                    outTag.Add(OUTPUT_PARAMETER_PREFIX + ident); 
                }
                //outTagJoined = String.Join(",", outTag.ToArray());
                //inTagJoined = String.Join(",", inTag.ToArray());
                List<object> allTags = new List<object>();
                foreach (string s in inTag) allTags.Add(s); 
                foreach (string s in outTag) allTags.Add(s); 

                string octaveFuncCall = String.Format(definition.OctaveFunctionDef, allTags.ToArray()); 
                ocs.Append(String.Format("\ntry {0} catch  {1} = 'Exception'; end",  
                        octaveFuncCall,outTag[0]));
            }
            // the name for the case directory will be postfixed with the arrayprovider types ('double',...)
            string localPostfixedTestCaseName = definition.TestCaseName + "_" + arrProv.GetIdentifierPostfix(); 
            string TestCaseOutputPath = Path.Combine(m_baseDirectory, RelativeTestCasePath); 
            TestCaseOutputPath = Path.Combine(TestCaseOutputPath,localPostfixedTestCaseName) 
                                        + Path.DirectorySeparatorChar; 
            if (Directory.Exists(TestCaseOutputPath)) {
                Directory.Delete(TestCaseOutputPath,true); 
            }
                Directory.CreateDirectory(TestCaseOutputPath); 
            // save matfile
            using (Stream outStream = new FileStream(TestCaseOutputPath + matInputFileName, FileMode.Create)) {
                matFile.Write(outStream);
            }
            // finish + save creation script
            ocs.Append(String.Format("\n\nsave ('{0}','{1}*','-v6');", matOutputFileName, OUTPUT_PARAMETER_PREFIX));
            File.WriteAllText(TestCaseOutputPath + OCTAVE_CREATION_SCRIPT_NAME, ocs.ToString());
            #region create test class file
            inTag.Clear(); outTag.Clear();
            List<string> inTagR = new List<string>(); 
            StringBuilder testRunFunc = new StringBuilder(); 
            // handles input parameter
            for (int i = 0; i < definition.InputParameterCount; i++) {
                testRunFunc.Append(String.Format(@"{1} {2}{0} = ({1})m_inputMatfile[""{2}_""+id.ToString()+""_{0}""];
                {2}{0}.Detach();"
                            ,i.ToString(),arrProv.GetCSharpTypeDefinition(),INPUT_PARAMETER_PREFIX));
                inTag.Add(INPUT_PARAMETER_PREFIX + i.ToString());
                inTagR.Add(INPUT_PARAMETER_PREFIX + i.ToString() + ".R"); 
            }
            // handles output parameter 
            StringBuilder errorCheckString = new StringBuilder(); 
            for (int i = 0; i < definition.OutputParameterCount; i++) {
                testRunFunc.Append(Environment.NewLine + "\t\t\t\t" 
                                + String.Format(@"ILBaseArray parameter{0} = null;",i)); 
                outTag.Add("parameter" + i.ToString());
                // error checking 
                errorCheckString.Append(String.Format(@"
                if (!(parameter{0}.IsEmpty & m_outputMatfile[""output_""+id.ToString()+""_{0}""].IsEmpty) &&
                     (!TestHelper.TestEqualSloppy(parameter{0},m_outputMatfile[""{1}_""+id.ToString()+""_{0}""]))) {{
                    Error(""result{0} does not match expected value! parameterset: #""+id.ToString()+"",input #0: "" + input0.Dimensions.ToString());
                    return ProcessParameterErrorInfo(id, parameter{0}, {2}, {3}, {0}, m_inputMatfile, m_outputMatfile,{4});
                }}", i.ToString(), OUTPUT_PARAMETER_PREFIX, definition.InputParameterCount, definition.OutputParameterCount, escape(definition.TargetFunctionDef)));
            }
            outTagJoined = String.Join(",", outTag.ToArray());
            inTagJoined = String.Join(",", inTag.ToArray());
            string inTagRJoined = String.Join(",", inTagR.ToArray()); 
            inTag.AddRange(outTag); 
            inTagR.AddRange(outTag); 
            string targetFunction = String.Format(definition.TargetFunctionDef, inTag.ToArray());
            // create dense test
            testRunFunc.Append(Environment.NewLine + "\t\t\t\t" +targetFunction);
            testRunFunc.Append(errorCheckString.ToString());
            // create reference test 
            targetFunction = String.Format(definition.TargetFunctionDef, inTagR.ToArray());
            testRunFunc.Append(Environment.NewLine + "\t\t\t\t" + targetFunction);
            testRunFunc.Append(errorCheckString.ToString());
            // finish
            string runtimeRelPath2matFiles = Path.Combine(m_baseDirectoryRelative2EXE, RelativeTestCasePath) + Path.DirectorySeparatorChar
                                + localPostfixedTestCaseName
                                + Path.DirectorySeparatorChar; 
            string testClass = String.Format(HEADER, localPostfixedTestCaseName, // 0
                                runtimeRelPath2matFiles + matInputFileName,
                                runtimeRelPath2matFiles + matOutputFileName,             // 1,2
                                inputVars.Dimensions[0].ToString(), 
                                testRunFunc.ToString(),
                                arrProv.GetCSharpTypeDefinition(),
                                runtimeRelPath2matFiles + resultOverridesFileName);
            File.WriteAllText(TestCaseOutputPath + localPostfixedTestCaseName + ".cs", testClass); 
            #endregion
            if (DeleteResultsOnRecreate) {
                if (File.Exists(TestCaseOutputPath + matOutputFileName)) {
                    File.Delete(TestCaseOutputPath + matOutputFileName); 
                }
            }
            #region write ResultOverrides 
            if (definition.ResultOverrides != null) {
                ILMatFile overridesMatFile = new ILMatFile(); 
                bool mustWriteOverrides = false; 
                foreach (ILResultOverride overr in definition.ResultOverrides) {
                    if (overr.TestArrayProvider == arrProv) {
                        // foreach override find matching parameters in current provider
                        for (int o = 0; o < overr.Inputs.Dimensions[0]; o++) {
                            for (int i = 0; i < inputVars.Dimensions[0]; i++) {
                                if (ILMath.isequalwithequalnans(
                                    overr.Inputs[o,null],inputVars[i,null])) {
                                    // matching override found
                                    System.Diagnostics.Debug.Assert(overr.Outputs.Dimensions[1] == inputVars.Dimensions[1]); 
                                    for (int c = 0; c < inputVars.Dimensions[1]; c++) {
                                        string ident = "_" + i.ToString() + "_" + c.ToString(); 
                                        overridesMatFile[OUTPUT_PARAMETER_PREFIX + ident] = overr.Outputs[o,c]; 
			                        }
                                    mustWriteOverrides = true; 
                                }
                            }
                        }
                    }
                }
                if (mustWriteOverrides)
                using (FileStream overridesStream = new FileStream(TestCaseOutputPath + OVERRIDES_PARAMETER_PREFIX + ".mat",FileMode.Create)) {
                    overridesMatFile.Write(overridesStream); 
                } else if (File.Exists(TestCaseOutputPath + OVERRIDES_PARAMETER_PREFIX + ".mat")) {
                    File.Delete(TestCaseOutputPath + OVERRIDES_PARAMETER_PREFIX + ".mat"); 
                }
            }
            #endregion 

            return localPostfixedTestCaseName; 
        }

        private string escape(string p) {
            string tmp = p; //.Replace("{","[{").Replace("[{","{{"); 
            //tmp = tmp.Replace("}","[}").Replace("[}","}}");
            return String.Format("\"{0}\"",tmp); 
        }
    }
}
